//    OpenVPN -- An application to securely tunnel IP networks
//               over a single port, with support for SSL/TLS-based
//               session authentication and key exchange,
//               packet encryption, packet authentication, and
//               packet compression.
//
//    Copyright (C) 2012-2022 OpenVPN Inc.
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU Affero General Public License Version 3
//    as published by the Free Software Foundation.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU Affero General Public License for more details.
//
//    You should have received a copy of the GNU Affero General Public License
//    along with this program in the COPYING file.
//    If not, see <http://www.gnu.org/licenses/>.

#pragma once

#include <string>
#include <cstring>
#include <cstdint>
#include <utility>

#include <openvpn/common/exception.hpp>
#include <openvpn/common/string.hpp>
#include <openvpn/common/number.hpp>
#include <openvpn/common/file.hpp>
#include <openvpn/common/jsonlib.hpp>
#include <openvpn/common/jsonhelperfmt.hpp>
#include <openvpn/buffer/bufstr.hpp>
#include <openvpn/buffer/bufstream.hpp>

#ifndef HAVE_JSON
#error no JSON library available
#endif

namespace openvpn {
namespace json {

OPENVPN_EXCEPTION(json_parse);

/* Workaround warnings in gcc 12/13, see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109642 */
#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ == 13
#define DISABLE_DANGLING_WARNINGS() \
    _Pragma("GCC diagnostic push")  \
        _Pragma("GCC diagnostic ignored \"-Wdangling-reference\"")

#define REENABLE_DANGLING_WARNINGS() \
    _Pragma("GCC diagnostic pop")
#else
#define DISABLE_DANGLING_WARNINGS()
#define REENABLE_DANGLING_WARNINGS()
#endif


template <typename TITLE>
inline Json::Value parse(const std::string &str, const TITLE &title)
{
#ifdef OPENVPN_JSON
    return Json::Value::parse(str, StringTempl::to_string(title));
#else
    Json::CharReaderBuilder builder;
    builder["collectComments"] = false;
    Json::Value root;
    std::string errors;
    std::istringstream instr(str);

    if (!Json::parseFromStream(builder, instr, &root, &errors))
        throw json_parse(StringTempl::to_string(title) + " : " + errors);
    return root;
#endif
}

inline Json::Value parse(const std::string &str)
{
    return parse(str, "json");
}

inline Json::Value parse_from_file(const std::string &fn)
{
    return parse(read_text_utf8(fn), fn);
}

template <typename BUFFER, typename TITLE>
inline Json::Value parse_from_buffer(const BUFFER &buf, const TITLE &title)
{
#ifdef OPENVPN_JSON
    return Json::Value::parse(buf, StringTempl::to_string(title));
#else
    Json::CharReaderBuilder builder;
    builder["collectComments"] = false;
    Json::Value root;
    std::string errors;

    std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
    if (!reader->parse(reinterpret_cast<const char *>(buf.c_data()), reinterpret_cast<const char *>(buf.c_data()) + buf.size(), &root, &errors))
        throw json_parse(StringTempl::to_string(title) + " : " + errors);
    return root;
#endif
}

#ifdef OPENVPN_JSON_INTERNAL
DISABLE_DANGLING_WARNINGS()
template <typename NAME, typename TITLE>
inline const Json::Value &cast(const Json::ValueType target_type,
                               const Json::Value &value,
                               const NAME &name,
                               const bool optional,
                               const TITLE &title)
{
    if (value.isNull())
    {
        if (optional)
            return value;
        throw json_parse(Json::Value::type_string(target_type) + " cast " + fmt_name(name, title) + " is null");
    }
    if (!value.isConvertibleTo(target_type))
        throw json_parse(Json::Value::type_string(target_type) + " cast " + fmt_name(name, title) + " is of incorrect type (" + value.type_string() + ')');
    return value;
}
REENABLE_DANGLING_WARNINGS()

template <typename NAME>
inline const Json::Value &cast(const Json::ValueType target_type,
                               const Json::Value &value,
                               const NAME &name,
                               const bool optional)
{
    return cast(target_type, value, name, optional, nullptr);
}

template <typename NAME, typename TITLE>
inline Json::Value cast(const Json::ValueType target_type,
                        Json::Value &&value,
                        const NAME &name,
                        const bool optional,
                        const TITLE &title)
{
    Json::Value ret = std::move(value);
    cast(target_type, ret, name, optional, title);
    return ret;
}

template <typename NAME>
inline Json::Value cast(const Json::ValueType target_type,
                        Json::Value &&value,
                        const NAME &name,
                        const bool optional)
{
    return cast(target_type, std::move(value), name, optional, nullptr);
}

#endif

template <typename T, typename NAME>
inline void from_vector(Json::Value &root, const T &vec, const NAME &name)
{
    Json::Value array(Json::arrayValue);
    for (auto &e : vec)
        array.append(e.to_json());
    if (array.size())
        root[name] = std::move(array);
}

template <typename TITLE>
inline void assert_dict(const Json::Value &obj, const TITLE &title)
{
    if (!obj.isObject())
        throw json_parse(fmt_name_cast(title) + " is not a JSON dictionary");
}

template <typename TITLE>
inline bool is_dict(const Json::Value &obj, const TITLE &title)
{
    if (obj.isNull())
        return false;
    assert_dict(obj, title);
    return true;
}

template <typename NAME>
inline bool exists(const Json::Value &root, const NAME &name)
{
    if (!root.isObject())
        return false;
    return !root[name].isNull();
}

template <typename NAME>
inline bool string_exists(const Json::Value &root, const NAME &name)
{
    if (!root.isObject())
        return false;
    return root[name].isString();
}

template <typename T, typename NAME, typename TITLE>
inline void to_vector(const Json::Value &root, T &vec, const NAME &name, const TITLE &title)
{
    const Json::Value &array = root[name];
    if (array.isNull())
        return;
    if (!array.isArray())
        throw json_parse("array " + fmt_name(name, title) + " is of incorrect type");
    for (unsigned int i = 0; i < array.size(); ++i)
    {
        vec.emplace_back();
        vec.back().from_json(array[i], fmt_name(name, title));
    }
}

template <typename NAME, typename TITLE>
inline std::string get_string(const Json::Value &root,
                              const NAME &name,
                              const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
        throw json_parse("string " + fmt_name(name, title) + " is missing");
    if (!value.isString())
        throw json_parse("string " + fmt_name(name, title) + " is of incorrect type");
    return value.asString();
}

template <typename NAME>
inline std::string get_string(const Json::Value &root, const NAME &name)
{
    return get_string(root, name, nullptr);
}

#ifdef OPENVPN_JSON_INTERNAL
template <typename NAME, typename TITLE>
inline const std::string &get_string_ref(const Json::Value &root,
                                         const NAME &name,
                                         const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
        throw json_parse("string " + fmt_name(name, title) + " is missing");
    if (!value.isString())
        throw json_parse("string " + fmt_name(name, title) + " is of incorrect type");
    return value.asStringRef();
}

template <typename NAME>
inline const std::string &get_string_ref(const Json::Value &root, const NAME &name)
{
    return get_string_ref(root, name, nullptr);
}

// for safety, define an rvalue ref overload that falls back to get_string()
template <typename NAME, typename TITLE>
inline std::string get_string_ref(Json::Value &&root,
                                  const NAME &name,
                                  const TITLE &title)
{
    return get_string(root, name, title);
}

template <typename NAME, typename TITLE>
inline const std::string *get_string_ptr(const Json::Value &root,
                                         const NAME &name,
                                         const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
        return nullptr;
    if (!value.isString())
        throw json_parse("string " + fmt_name(name, title) + " is of incorrect type");
    return value.asStringPtr();
}

template <typename NAME>
inline const std::string *get_string_ptr(const Json::Value &root, const NAME &name)
{
    return get_string_ptr(root, name, nullptr);
}
#endif

template <typename NAME, typename TITLE>
inline std::string get_string_optional(const Json::Value &root,
                                       const NAME &name,
                                       const std::string &default_value,
                                       const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
        return default_value;
    if (!value.isString())
        throw json_parse("string " + fmt_name(name, title) + " is of incorrect type");
    return value.asString();
}

template <typename NAME>
inline std::string get_string_optional(const Json::Value &root,
                                       const NAME &name,
                                       const std::string &default_value)
{
    return get_string_optional(root, name, default_value, nullptr);
}

template <typename TITLE>
inline std::string get_string_from_array(const Json::Value &root,
                                         const Json::ArrayIndex index,
                                         const TITLE &title)
{
    const Json::Value &value = root[index];
    if (value.isNull())
        throw json_parse("string " + fmt_name(index, title) + " is missing");
    if (!value.isString())
        throw json_parse("string " + fmt_name(index, title) + " is of incorrect type");
    return value.asString();
}

inline std::string get_string_from_array(const Json::Value &root,
                                         const Json::ArrayIndex index)
{
    return get_string_from_array(root, index, nullptr);
}

template <typename NAME, typename TITLE>
inline int get_int(const Json::Value &root,
                   const NAME &name,
                   const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
        throw json_parse("int " + fmt_name(name, title) + " is missing");
    if (!value.isInt())
        throw json_parse("int " + fmt_name(name, title) + " is of incorrect type");
    return value.asInt();
}

template <typename NAME>
inline int get_int(const Json::Value &root, const NAME &name)
{
    return get_int(root, name, nullptr);
}

template <typename NAME, typename TITLE>
inline int get_int_optional(const Json::Value &root,
                            const NAME &name,
                            const int default_value,
                            const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
        return default_value;
    if (!value.isInt())
        throw json_parse("int " + fmt_name(name, title) + " is of incorrect type");
    return value.asInt();
}

template <typename NAME>
inline int get_int_optional(const Json::Value &root,
                            const NAME &name,
                            const int default_value)
{
    return get_int_optional(root, name, default_value, nullptr);
}

template <typename NAME, typename TITLE>
inline unsigned int get_uint(const Json::Value &root,
                             const NAME &name,
                             const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
        throw json_parse("uint " + fmt_name(name, title) + " is missing");
    if (!value.isUInt())
        throw json_parse("uint " + fmt_name(name, title) + " is of incorrect type");
    return value.asUInt();
}

template <typename NAME>
inline unsigned int get_uint(const Json::Value &root, const NAME &name)
{
    return get_uint(root, name, nullptr);
}

template <typename NAME, typename TITLE>
inline unsigned int get_uint_optional(const Json::Value &root,
                                      const NAME &name,
                                      const unsigned int default_value,
                                      const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
        return default_value;
    if (!value.isUInt())
        throw json_parse("uint " + fmt_name(name, title) + " is of incorrect type");
    return value.asUInt();
}

template <typename NAME>
inline unsigned int get_uint_optional(const Json::Value &root,
                                      const NAME &name,
                                      const unsigned int default_value)
{
    return get_uint_optional(root, name, default_value, nullptr);
}

template <typename NAME, typename TITLE>
inline unsigned int get_uint_via_string(const Json::Value &root,
                                        const NAME &name,
                                        const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
        throw json_parse("uint-via-string " + fmt_name(name, title) + " is missing");
    if (!value.isString())
        throw json_parse("uint-via-string " + fmt_name(name, title) + " is of incorrect type");

    unsigned int ret;
    if (!parse_number(value.asString(), ret))
        throw json_parse("uint-via-string " + fmt_name(name, title) + " failed to parse");
    return ret;
}

template <typename NAME>
inline unsigned int get_uint_via_string(const Json::Value &root,
                                        const NAME &name)
{
    return get_uint_via_string(root, name, nullptr);
}

template <typename NAME, typename TITLE>
inline unsigned int get_uint_optional_via_string(const Json::Value &root,
                                                 const NAME &name,
                                                 const unsigned int default_value,
                                                 const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
        return default_value;
    if (!value.isString())
        throw json_parse("uint-via-string " + fmt_name(name, title) + " is of incorrect type");

    unsigned int ret;
    if (!parse_number(value.asString(), ret))
        throw json_parse("uint-via-string " + fmt_name(name, title) + " failed to parse");
    return ret;
}

template <typename NAME>
inline unsigned int get_uint_optional_via_string(const Json::Value &root,
                                                 const NAME &name,
                                                 const unsigned int default_value)
{
    return get_uint_optional_via_string(root, name, default_value, nullptr);
}

template <typename NAME, typename TITLE>
inline std::uint64_t get_uint64(const Json::Value &root,
                                const NAME &name,
                                const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
        throw json_parse("uint64 " + fmt_name(name, title) + " is missing");
    if (!value.isUInt64())
        throw json_parse("uint64 " + fmt_name(name, title) + " is of incorrect type");
    return value.asUInt64();
}

template <typename NAME>
inline std::uint64_t get_uint64(const Json::Value &root, const NAME &name)
{
    return get_uint64(root, name, nullptr);
}

template <typename NAME, typename TITLE>
inline std::uint64_t get_uint64_optional(const Json::Value &root,
                                         const NAME &name,
                                         const std::uint64_t default_value,
                                         const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
        return default_value;
    if (!value.isUInt64())
        throw json_parse("uint64 " + fmt_name(name, title) + " is of incorrect type");
    return value.asUInt64();
}

template <typename NAME, typename TITLE>
inline std::int64_t get_int64_optional(const Json::Value &root,
                                       const NAME &name,
                                       const std::uint64_t default_value,
                                       const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
        return default_value;
    if (!value.isInt64())
        throw json_parse("int64 " + fmt_name(name, title) + " is of incorrect type");
    return value.asInt64();
}

template <typename NAME>
inline std::uint64_t get_uint64_optional(const Json::Value &root,
                                         const NAME &name,
                                         const std::uint64_t default_value)
{
    return get_uint64_optional(root, name, default_value, nullptr);
}

/*
 * The get_integer_optional function are used to select the right
 * method based on the default_value parameter
 */

template <typename NAME, typename TITLE>
std::uint64_t get_integer_optional(const Json::Value &root,
                                   const NAME &name,
                                   const std::uint64_t default_value,
                                   const TITLE &title)
{
    return get_uint64_optional(root, name, default_value, title);
}

template <typename NAME, typename TITLE>
std::int64_t get_integer_optional(const Json::Value &root,
                                  const NAME &name,
                                  const std::int64_t default_value,
                                  const TITLE &title)
{
    return get_int64_optional(root, name, default_value, title);
}

template <typename NAME, typename TITLE>
inline unsigned int get_integer_optional(const Json::Value &root,
                                         const NAME &name,
                                         const unsigned int default_value,
                                         const TITLE &title)
{
    return get_uint_optional(root, name, default_value, title);
}

template <typename NAME, typename TITLE>
inline int get_integer_optional(const Json::Value &root,
                                const NAME &name,
                                const int default_value,
                                const TITLE &title)
{
    return get_int_optional(root, name, default_value, title);
}

template <typename NAME, typename TITLE>
inline std::uint64_t get_uint64_via_string(const Json::Value &root,
                                           const NAME &name,
                                           const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
        throw json_parse("uint64-via-string " + fmt_name(name, title) + " is missing");
    if (!value.isString())
        throw json_parse("uint64-via-string " + fmt_name(name, title) + " is of incorrect type");

    std::uint64_t ret;
    if (!parse_number(value.asString(), ret))
        throw json_parse("uint64-via-string " + fmt_name(name, title) + " failed to parse");
    return ret;
}

template <typename NAME>
inline std::uint64_t get_uint64_via_string(const Json::Value &root,
                                           const NAME &name)
{
    return get_uint64_via_string(root, name, nullptr);
}

template <typename NAME, typename TITLE>
inline std::uint64_t get_uint64_optional_via_string(const Json::Value &root,
                                                    const NAME &name,
                                                    const std::uint64_t default_value,
                                                    const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
        return default_value;
    if (!value.isString())
        throw json_parse("uint64-via-string " + fmt_name(name, title) + " is of incorrect type");

    std::uint64_t ret;
    if (!parse_number(value.asString(), ret))
        throw json_parse("uint64-via-string " + fmt_name(name, title) + " failed to parse");
    return ret;
}

template <typename NAME>
inline std::uint64_t get_uint64_optional_via_string(const Json::Value &root,
                                                    const NAME &name,
                                                    const std::uint64_t default_value)
{
    return get_uint64_optional_via_string(root, name, default_value, nullptr);
}
template <typename NAME, typename TITLE>
inline bool get_bool(const Json::Value &root,
                     const NAME &name,
                     const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
        throw json_parse("bool " + fmt_name(name, title) + " is missing");
    if (!value.isBool())
        throw json_parse("bool " + fmt_name(name, title) + " is of incorrect type");
    return value.asBool();
}

template <typename NAME>
inline bool get_bool(const Json::Value &root, const NAME &name)
{
    return get_bool(root, name, nullptr);
}

template <typename NAME>
inline bool get_bool_optional(const Json::Value &root,
                              const NAME &name,
                              const bool default_value = false)
{
    const Json::Value &jv = root[name];
    if (jv.isConvertibleTo(Json::booleanValue))
        return jv.asBool();
    else
        return default_value;
}

template <typename NAME>
inline int get_bool_tristate(const Json::Value &root,
                             const NAME &name)
{
    const Json::Value &jv = root[name];
    if (jv.isConvertibleTo(Json::booleanValue))
        return jv.asBool() ? 1 : 0;
    else
        return -1;
}

DISABLE_DANGLING_WARNINGS()
template <typename NAME, typename TITLE>
inline const Json::Value &get_dict(const Json::Value &root,
                                   const NAME &name,
                                   const bool optional,
                                   const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
    {
        if (optional)
            return value;
        throw json_parse("dictionary " + fmt_name(name, title) + " is missing");
    }
    if (!value.isObject())
        throw json_parse("dictionary " + fmt_name(name, title) + " is of incorrect type");
    return value;
}
REENABLE_DANGLING_WARNINGS()

template <typename NAME>
inline const Json::Value &get_dict(const Json::Value &root,
                                   const NAME &name,
                                   const bool optional)
{
    return get_dict(root, name, optional, nullptr);
}

template <typename NAME, typename TITLE>
inline Json::Value get_dict(Json::Value &&root,
                            const NAME &name,
                            const bool optional,
                            const TITLE &title)
{
    Json::Value r = std::move(root);
    return get_dict(r, name, optional, title);
}

template <typename NAME>
inline Json::Value get_dict(Json::Value &&root,
                            const NAME &name,
                            const bool optional)
{
    return get_dict(std::move(root), name, optional, nullptr);
}

DISABLE_DANGLING_WARNINGS()
template <typename TITLE>
inline const Json::Value &cast_dict(const Json::Value &value,
                                    const bool optional,
                                    const TITLE &title)
{
    if (value.isNull())
    {
        if (optional)
            return value;
        throw json_parse("dictionary cast " + fmt_name_cast(title) + " is null");
    }
    if (!value.isObject())
        throw json_parse("dictionary cast " + fmt_name_cast(title) + " is of incorrect type");
    return value;
}
REENABLE_DANGLING_WARNINGS()

inline const Json::Value &cast_dict(const Json::Value &value,
                                    const bool optional)
{
    return cast_dict(value, optional, nullptr);
}

template <typename TITLE>
inline Json::Value cast_dict(Json::Value &&value,
                             const bool optional,
                             const TITLE &title)
{
    Json::Value ret = std::move(value);
    cast_dict(ret, optional, title);
    return ret;
}

inline Json::Value cast_dict(Json::Value &&value,
                             const bool optional)
{
    return cast_dict(std::move(value), optional, nullptr);
}

DISABLE_DANGLING_WARNINGS()
template <typename NAME, typename TITLE>
inline const Json::Value &get_array(const Json::Value &root,
                                    const NAME &name,
                                    const bool optional,
                                    const TITLE &title)
{
    const Json::Value &value = root[name];
    if (value.isNull())
    {
        if (optional)
            return value;
        throw json_parse("array " + fmt_name(name, title) + " is missing");
    }
    if (!value.isArray())
        throw json_parse("array " + fmt_name(name, title) + " is of incorrect type");
    return value;
}
REENABLE_DANGLING_WARNINGS()

template <typename NAME>
inline const Json::Value &get_array(const Json::Value &root,
                                    const NAME &name,
                                    const bool optional)
{
    return get_array(root, name, optional, nullptr);
}

template <typename NAME, typename TITLE>
inline Json::Value get_array(Json::Value &&root,
                             const NAME &name,
                             const bool optional,
                             const TITLE &title)
{
    Json::Value r = std::move(root);
    return get_array(r, name, optional, title);
}

template <typename NAME>
inline Json::Value get_array(Json::Value &&root,
                             const NAME &name,
                             const bool optional)
{
    return get_array(std::move(root), name, optional, nullptr);
}

DISABLE_DANGLING_WARNINGS()
template <typename TITLE>
inline const Json::Value &cast_array(const Json::Value &value,
                                     const bool optional,
                                     const TITLE &title)
{
    if (value.isNull())
    {
        if (optional)
            return value;
        throw json_parse("array cast " + fmt_name_cast(title) + " is null");
    }
    if (!value.isArray())
        throw json_parse("array cast " + fmt_name_cast(title) + " is of incorrect type");
    return value;
}
REENABLE_DANGLING_WARNINGS()

inline const Json::Value &cast_array(const Json::Value &value,
                                     const bool optional)
{
    return cast_array(value, optional, nullptr);
}

template <typename TITLE>
inline Json::Value cast_array(Json::Value &&value,
                              const bool optional,
                              const TITLE &title)
{
    Json::Value ret = std::move(value);
    cast_array(ret, optional, title);
    return ret;
}

inline Json::Value cast_array(Json::Value &&value,
                              const bool optional)
{
    return cast_array(std::move(value), optional, nullptr);
}

template <typename NAME, typename TITLE>
inline void to_string(const Json::Value &root,
                      std::string &dest,
                      const NAME &name,
                      const TITLE &title)
{
    dest = get_string(root, name, title);
}

template <typename NAME, typename TITLE>
inline void to_string_optional(const Json::Value &root,
                               std::string &dest,
                               const NAME &name,
                               const std::string &default_value,
                               const TITLE &title)
{
    dest = get_string_optional(root, name, default_value, title);
}

template <typename NAME, typename TITLE>
inline void to_int(const Json::Value &root,
                   int &dest,
                   const NAME &name,
                   const TITLE &title)
{
    dest = get_int(root, name, title);
}

template <typename NAME, typename TITLE>
inline void to_uchar(const Json::Value &root,
                     unsigned char &dest,
                     const NAME &name,
                     const TITLE &title)
{
    auto temp = get_int(root, name, title);
    dest = clamp_notify<unsigned char>(temp,
                                       [](decltype(temp) temp) -> unsigned char
                                       { 
        auto why = std::string("Conversion error [" + std::to_string(temp) + "] to unsigned char");
        throw json_parse(std::move(why)); });
}

template <typename NAME, typename TITLE>
inline void to_uint(const Json::Value &root,
                    unsigned int &dest,
                    const NAME &name,
                    const TITLE &title)
{
    dest = get_uint(root, name, title);
}

template <typename NAME, typename TITLE>
inline void to_uint_optional(const Json::Value &root,
                             unsigned int &dest,
                             const NAME &name,
                             const unsigned int default_value,
                             const TITLE &title)
{
    dest = get_uint_optional(root, name, default_value, title);
}

template <typename NAME, typename TITLE>
inline void to_uint64(const Json::Value &root,
                      std::uint64_t &dest,
                      const NAME &name,
                      const TITLE &title)
{
    dest = get_uint64(root, name, title);
}

template <typename NAME, typename TITLE>
inline void to_bool(const Json::Value &root,
                    bool &dest,
                    const NAME &name,
                    const TITLE &title)
{
    dest = get_bool(root, name, title);
}

inline void format_compact(const Json::Value &root, Buffer &buf)
{
#ifdef OPENVPN_JSON
    root.toCompactString(buf);
#else
    Json::StreamWriterBuilder json_builder;
    json_builder.settings_["indentation"] = "";
    BufferStreamOut os(buf);
    std::unique_ptr<Json::StreamWriter> sw(json_builder.newStreamWriter());
    sw->write(root, &os);
#endif
}

inline std::string format_compact(const Json::Value &root,
                                  const size_t size_hint = 256)
{
    BufferPtr bp = new BufferAllocated(size_hint, BufferAllocated::GROW);
    format_compact(root, *bp);
    return buf_to_string(*bp);
}

inline void format(const Json::Value &root, Buffer &buf)
{
#ifdef OPENVPN_JSON
    root.toStyledString(buf);
#else
    Json::StreamWriterBuilder json_builder;
    json_builder.settings_["indentation"] = "  ";
    BufferStreamOut os(buf);
    std::unique_ptr<Json::StreamWriter> sw(json_builder.newStreamWriter());
    sw->write(root, &os);
#endif
}

inline std::string format(const Json::Value &root)
{
    return root.toStyledString();
}

inline std::string error(const Json::Value &root)
{
    const Json::Value &je = root["error"];
    if (je.isString())
        return je.asString();
    else
        return std::string();
}

// Guarantee that json object jr is a dictionary.
// Do this by encapsulating jr in a dictionary
// { "result": jr } if it is not already one.
inline Json::Value dict_result(Json::Value jr)
{
    if (jr.isObject())
        return jr;
    else
    {
        Json::Value jret(Json::objectValue);
        jret["result"] = std::move(jr);
        return jret;
    }
}
} // namespace json
} // namespace openvpn
